/* @flow */

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop,
} from "../util/index";

import { traverse } from "./traverse";
import { queueWatcher } from "./scheduler";
import Dep, { pushTarget, popTarget } from "./dep";

import type { SimpleSet } from "../util/index";

let uid = 0;

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm;
    if (isRenderWatcher) {
      vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if (options) {
      this.deep = !!options.deep;
      this.user = !!options.user; /* 用户watcher  */
      this.lazy = !!options.lazy; /* 判断是否为 computed watcher , false 值会走缓存 ，true 重新计算*/
      this.sync = !!options.sync;
      this.before = options.before;
    } else {
      this.deep = this.user = this.lazy = this.sync = false;
    }
    this.cb = cb; /* 用户watcher 时的回调 */
    this.id = ++uid; // 循环时控制顺序
    this.active = true;
    this.dirty = this.lazy; // 同 lazy , 不同的是 lazy不变 ， 通过改变dirty来控制缓存
    this.deps = []; /*  watcher 的依赖 */
    this.newDeps = [];
    this.depIds = new Set();
    this.newDepIds = new Set();
    this.expression =
      process.env.NODE_ENV !== "production" ? expOrFn.toString() : "";
    // parse expression for getter
    if (typeof expOrFn === "function") {
      this.getter = expOrFn; /* 渲染watcher ，computed */
    } else {
      this.getter = parsePath(expOrFn); /* 用户watcher 时 */
      if (!this.getter) {
        /*  */
        this.getter = noop;
        process.env.NODE_ENV !== "production" &&
          warn(
            `Failed watching path: "${expOrFn}" ` +
              "Watcher only accepts simple dot-delimited paths. " +
              "For full control, use a function instead.",
            vm
          );
      }
    }
    /* 如果非计算watcher  会调用get计算value , 反之 计算watcher会在 computed触发 get时去计算*/
    /* 这里也就是所说的 声明了computed但不使用就不会收集依赖 */
    this.value = this.lazy ? undefined : this.get();
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get() {
    /* 当前 watch入栈 */
    pushTarget(this);
    let value;
    const vm = this.vm;
    try {
      /* 调用 expOrFn 要么视图， 渲染 ， 依赖收集 于此开始*/
      /* 主要触发被观察者的 get */
      value = this.getter.call(vm, vm);
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`);
      } else {
        throw e;
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      /* 依赖收集后将当前watcher出栈 */
      popTarget();
      this.cleanupDeps();
    }
    return value;
  }

  /**
   * Add a dependency to this directive.
   */
  /* 存放当前dep 作为相互依赖 */
  addDep(dep: Dep) {
    const id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps() {
    let i = this.deps.length;
    while (i--) {
      const dep = this.deps[i];
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this);
      }
    }
    let tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  }

  /* 根据watcher类型触发更新 */
  update() {
    /* 修改计算watcher 属性 dirty ，在视图watcher渲染时遇到cmputed触发get */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      /*  */
      this.run();
    } else {
      /* 将watcher推入队列中 使用 nextTick 运行 */
      /* 避免多次触发时多次更新 */
      queueWatcher(this);
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  /* 用户watcher时，触发回调 */
  run() {
    if (this.active) {
      /* 新值 */
      const value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        /* 当前value为老值 */
        const oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(
              e,
              this.vm,
              `callback for watcher "${this.expression}"`
            );
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  }

  /* computed计算 */
  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }
  /* 计算 watcher专用方法 */
  depend() {
    /* 获取当前watcher依赖 */
    let i = this.deps.length;
    /* 将视图watcher添加到计算属性的依赖中，如果存在就说明 计算watcher的依赖项已经在视图中使用，反之则不存在，间接为依赖添加了watcher */
    while (i--) {
      /* Dep的depend方法 */
      /* 为自己的依赖项添加视图watcher */
      this.deps[i].depend();
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown() {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this);
      }
      let i = this.deps.length;
      while (i--) {
        this.deps[i].removeSub(this);
      }
      this.active = false;
    }
  }
}
